#define FUSE_USE_VERSION 26
#include <fuse.h>
#include <string>
#include <cstdlib>
#include <cerrno>
#include "tagfs.h"
#include "fusecpp.h"

TagFS tagfs;

// --------------------------------------
static int tag_mkdir(const char*, mode_t) { return -EPERM; }
// --------------------------------------

// ---------------------------------------------
static int tag_mknod(const char*, mode_t, dev_t) { return -EPERM; }
// ---------------------------------------------

// ---------------------------------------------
static int tag_symlink(const char*, const char*) { return -EPERM; }
// ---------------------------------------------

// ------------------------------------------
static int tag_link(const char*, const char*) { return -EPERM; }
// ------------------------------------------

// ------------------------------
static int tag_rmdir(const char*) { return -EPERM; }
// ------------------------------

// ------------------------------------------------
static int tag_readlink(const char*, char*, size_t) { return -EPERM; }
// ------------------------------------------------

// --------------------------------------------
static int tag_chown(const char*, uid_t, gid_t) { return 0; }
// --------------------------------------------

// --------------------------------------
static int tag_chmod(const char*, mode_t) { return 0; }
// --------------------------------------

// --------------------------------------------------------
static int tag_opendir(const char*, struct fuse_file_info*) { return 0; }
// --------------------------------------------------------

// -----------------------------------------------------------
static int tag_releasedir(const char*, struct fuse_file_info*) { return 0; }
// -----------------------------------------------------------

// --------------------------------------------------------------
static int tag_fsyncdir(const char*, int, struct fuse_file_info*) { return 0; }
// --------------------------------------------------------------

// ------------------------------------------------------
static int tag_flush(const char*, struct fuse_file_info*) { return 0; }
// ------------------------------------------------------

// -----------------------------------------------------------
static int tag_fsync(const char*, int, struct fuse_file_info*) { return 0; }
// -----------------------------------------------------------

// ********************************************************************

// ----------------------------------------------------------------------------------------
static int tag_fgetattr(const char* cpath, struct stat* stbuf, struct fuse_file_info* info)
// ----------------------------------------------------------------------------------------
{
	memset(stbuf, 0, sizeof(struct stat));
	std::string path;
	path.assign(cpath);
	return tagfs.fgetattr(path, stbuf, info);

}
// ----------------------------------------------------------
static int tag_getattr(const char* cpath, struct stat* stbuf) { return tag_fgetattr(cpath, stbuf, NULL); }
// ----------------------------------------------------------

// ------------------------------
static int tag_readdir(const char* cpath, void* buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi)
// ------------------------------
{
	std::string str;
	str.assign(cpath);
	std::list<std::string> list;
	std::list<struct stat> statlist;
	if (!tagfs.readdir(str, &list, &statlist))
	{
		return -EINVAL;
	}
	filler(buf, ".", NULL, 0);
	filler(buf, "..", NULL, 0);
	struct stat stbuf;
	while (!list.empty())
	{
		stbuf = statlist.front();
		filler(buf, list.front().data(), &stbuf, 0);
		list.pop_front();
		statlist.pop_front();
	}
	return 0;
}

// ----------------------------------------------------------------
static int tag_open(const char* cpath, struct fuse_file_info* info)
// ----------------------------------------------------------------
{
	std::string path;
	path.assign(cpath);
	return tagfs.open(path, info);
}
// -------------------------------------------------------------------------------
static int tag_create(const char* cpath, mode_t mode, struct fuse_file_info* info) { return tag_open(cpath, info); }
// -------------------------------------------------------------------------------

// ---------------------------------------------------------------------------------
static int tag_ftruncate(const char* cpath, off_t size, struct fuse_file_info* info)
// ---------------------------------------------------------------------------------
{
	if (size < 0)
	{
		return -EINVAL;
	}
	std::string path;
	path.assign(cpath);
	return tagfs.ftruncate(path, size, info);
}
// ---------------------------------------------------
static int tag_truncate(const char* cpath, off_t size) { return tag_ftruncate(cpath, size, NULL); }
// ---------------------------------------------------

// ---------------------------------------
static int tag_unlink (const char * cpath)
// ---------------------------------------
{
	std::string path;
	path.assign(cpath);
	return tagfs.unlink(path);
}

// -----------------------------------------------------------------------------------------------------
static int tag_read(const char* cpath, char* out, size_t len, off_t offset, struct fuse_file_info* info)
// -----------------------------------------------------------------------------------------------------
{
	if (len < 0)
	{
		return -EINVAL;
	}
	std::string path;
	path.assign(cpath);
	return tagfs.read(path, out, len, offset, info);
}

// -----------------------------------------------------------------------------------------------------
static int tag_write(const char* cpath, const char* in, size_t len, off_t offset, struct fuse_file_info* info)
// -----------------------------------------------------------------------------------------------------
{
	if (len < 0)
	{
		return -EINVAL;
	}
	std::string path;
	path.assign(cpath);
	return tagfs.write(path, in, len, offset, info);
}

// -------------------------------------------------------------------
static int tag_release(const char* cpath, struct fuse_file_info* info)
// -------------------------------------------------------------------
{
	std::string path;
	path.assign(cpath);
	return tagfs.release(path, info);	
}

// -------------------------------------------------------------------
static void* tag_init(struct fuse_conn_info*)
// -------------------------------------------------------------------
{
	return NULL;
}

// -------------------------------------------------------------------
static void tag_destroy(void* data)
// -------------------------------------------------------------------
{
	tagfs.destroy();	
}

// --------------------------------------------------------------
static int tag_utimens(const char* cpath, const struct timespec tv[2])
// --------------------------------------------------------------
{
	std::string path;
	path.assign(cpath);
	return tagfs.utimens(path, tv);	
}

// --------------------------------------------------------------
static int tag_rename(const char* coldpath, const char* cnewpath)
// --------------------------------------------------------------
{
	std::string oldpath, newpath;
	oldpath.assign(coldpath);
	newpath.assign(cnewpath);
	return tagfs.rename(oldpath, newpath);
}

// --------------------------------------------------------------
static int tag_access(const char* cpath, int mode)
// --------------------------------------------------------------
{
	std::string path;
	path.assign(cpath);
	return tagfs.access(path, mode);
}

// --------------------------------------------------------------
static int tag_statfs(const char* cpath, struct statvfs* vfs)
// --------------------------------------------------------------
{
	std::string path;
	path.assign(cpath);
	return tagfs.statfs(path, vfs);
}

// ---------------------------------------------------------------------------------
static int tag_getxattr(const char* cpath, const char* cname, char* buf, size_t size)
// ---------------------------------------------------------------------------------
{
	if (size < 0)
	{
		return -EINVAL;
	}
	std::string path, name;
	path.assign(cpath);
	name.assign(cname);
	return tagfs.getxattr(path, name, buf, size);
}

// --------------------------------------------------------------
static int tag_listxattr(const char* cpath, char* buf, size_t size)
// --------------------------------------------------------------
{
	if (size < 0)
	{
		return -EINVAL;
	}
	std::string path;
	path.assign(cpath);
	return tagfs.listxattr(path, buf, size);
}

// ------------------------------------------------------------------------------------------------------
static int tag_setxattr(const char* cpath, const char* cname, const char* cvalue, size_t size, int flags)
// ------------------------------------------------------------------------------------------------------
{
	if (size < 0)
	{
		return -EINVAL;
	}
	std::string path, name, value;
	path.assign(cpath);
	name.assign(cname);
	value.assign(cvalue, size);
	return tagfs.setxattr(path, name, value, size, flags);
}

// -------------------------------------------------------------
static int tag_removexattr(const char* cpath, const char* cname)
// -------------------------------------------------------------
{
	std::string path, name;
	path.assign(cpath);
	name.assign(cname);
	return tagfs.removexattr(path, name);
}

// ---------------------------------------------
// FUSE OBJECT DECLARATION

fuse_cpp::FuseDispatcher fuseD;
static struct fuse_operations* tag_fuse_make()
{
	// stubs
	fuseD.set_mknod(tag_mknod);
	fuseD.set_link(tag_link);
	fuseD.set_symlink(tag_symlink);
	fuseD.set_readlink(tag_readlink);
	fuseD.set_flush(tag_flush);
	fuseD.set_fsync(tag_fsync);
	fuseD.set_chown(tag_chown);
	fuseD.set_chmod(tag_chmod);
	fuseD.set_mkdir(tag_mkdir);
	fuseD.set_rmdir(tag_rmdir);
	fuseD.set_opendir(tag_opendir);
	fuseD.set_releasedir(tag_releasedir);
	fuseD.set_fsyncdir(tag_fsyncdir);
	// redirects
	fuseD.set_truncate(tag_truncate);
	fuseD.set_create(tag_create);
	fuseD.set_getattr(tag_getattr);
	// non-stubs
	fuseD.set_fgetattr(tag_fgetattr);
	fuseD.set_readdir(tag_readdir);
	fuseD.set_open(tag_open);
	fuseD.set_write(tag_write);
	fuseD.set_ftruncate(tag_ftruncate);
	fuseD.set_unlink(tag_unlink);
	fuseD.set_read(tag_read);
	fuseD.set_release(tag_release);
	fuseD.set_init(tag_init);
	fuseD.set_destroy(tag_destroy);
	fuseD.set_utimens(tag_utimens);
	fuseD.set_rename(tag_rename);
	fuseD.set_access(tag_access);
	fuseD.set_statfs(tag_statfs);
	fuseD.set_getxattr(tag_getxattr);
	fuseD.set_listxattr(tag_listxattr);
	fuseD.set_setxattr(tag_setxattr);
	fuseD.set_removexattr(tag_removexattr);

	return fuseD.get_fuseOps();
}
// ---------------------------------------------

// --------------
void print_help()
// --------------
{
	printf("Usage: mount.stagfs <node-path> <mount-point> [-d]\n");
	printf("Mounts SimpleTagFS filesystem from <node-path> on <mount-point>.\n");
	printf("       -d  print fuse output in foreground\n");
	printf("       This forwards -d option to fuse.\n");
	printf("More information available at http://asth.net/(+blog).\n");
	printf("THIS SOFTWARE COMES WITH NO WARRANTY. USE AT YOUR OWN RISK.\n");
}

// -----------------------------
int main(int argc, char *argv[])
// -----------------------------
{
	if (argc == 3 || argc == 4)
	{
		struct stat nod;
		if (stat(argv[1], &nod) != 0)
		{
			printf("Error: Couldn't stat source node: %s", strerror(errno));
			return EXIT_FAILURE;
		}
		
		FILE* fs = fopen(argv[1], "r+");

		if (fs == NULL)
		{
			printf("Error: Couldn't open source node: %s\n", strerror(errno));
			return EXIT_FAILURE;
		}

		if (tagfs.init(fs) == false)
		{
			fclose(fs);
			printf("Error: Source node is not a SimpleTagFS filesystem.\n");
			return EXIT_FAILURE;
		}
		char* fuse_argv[5];
		std::string argv_strings[3];
		argv_strings[0] = "-o";
		argv_strings[1] = "big_writes,atomic_o_trunc,ac_attr_timeout=1,default_permissions,hard_remove,fsname=SimpleTagFS,max_read=65535,max_write=65535";
		argv_strings[2] = "-d";
		fuse_argv[0] = argv[0];
		fuse_argv[1] = argv[2];
		fuse_argv[2] = (char*)argv_strings[0].data();
		fuse_argv[3] = (char*)argv_strings[1].data();
		fuse_argv[4] = (char*)argv_strings[2].data();
		if (argc == 4)
		{
			return fuse_main(5, fuse_argv, tag_fuse_make(), NULL);			
		}		
		return fuse_main(4, fuse_argv, tag_fuse_make(), NULL);
	}
	else
	{
		print_help();
		return EXIT_FAILURE;
	}
}
